/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org/scrmgr

    Copyright (C) 1991-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/
/* smscreen.c - interactive screen routines */

#include <time.h>
#include <setjmp.h>

#define SM_CLASS extern    
#include "smlib.h"
#include "smio.h"

CHAR *smscreen_rcsid(void) {
    return("$Id: smscreen.c,v 1.6 2008/02/19 13:15:50 bhepple Exp $");
}

struct sm_screen_control {
    SM_SCREEN 	screen;
    SM_FIELD	detail_field;
} sm_screen_stack[SM_MAX_POPUP];
int sm_top_of_screen_stack = 0;

/*
 *
 * screen and window control and enquiry
 *
 */

int sm_popup_win ARG4(int, num_rows, int, num_cols, int, row, int, col) 
{
    int		window;

    if ((window = smio_popup(num_rows, num_cols, row, col)) < 0)
	return(SM_ERROR);
    sm_top_of_screen_stack++;
    sm_screen_stack[window].screen = NULL;
    sm_screen_stack[window].detail_field = NULL;
    return(window);
}

void sm_del_win ARG1(int, window) 
{
    smio_delwin(window);
    if (sm_top_of_screen_stack)
	sm_top_of_screen_stack--;
}

static void _sm_paint_scr ARG5(int, 		window, 
			       SM_SCREEN, 	screen, 
			       int, 		enhance, 
			       int, 		recursep,
			       int,		do_base)
{
    SM_FIELD		*fieldp;
    SM_BACKGROUND	*bgp;
    int			temp;

    assert(screen);

    if (screen->scrolling && (screen->current_line < (screen->scrolling - 1)))
	screen->current_line = screen->scrolling - 1;
    
    if (do_base)
	for (bgp = sm_first_bg(screen); 
	     bgp && *bgp; 
	     bgp = sm_next_bg(bgp)) {
	    temp = (*bgp)->row;
	    if (screen->scrolling && ((*bgp)->row >= (screen->scrolling - 1)))
		(*bgp)->row = screen->current_line;
#if SM_XAW
	    if (smio_using_xaw)
		smxaw_create_bg(*bgp);
	    else
#endif
	    sm_paint_bg(window, *bgp);
	    (*bgp)->row = temp;
	}
    
    for (fieldp = sm_first_fld(screen); 
	 fieldp && *fieldp; 
	 fieldp = sm_next_fld(fieldp)) {

	temp = (*fieldp)->row;
	if (screen->scrolling && ((*fieldp)->row >= (screen->scrolling - 1)))
	    (*fieldp)->row = screen->current_line;
#if SM_XAW
	if (smio_using_xaw && do_base)
	    smxaw_create_fld(*fieldp);
	else
#endif
	sm_paint_fld(window, (*fieldp)->c_value, *fieldp, enhance, do_base);

	/* Recursively paint master screens if defined ... */
	if (recursep && (*fieldp)->master_screen)
	    _sm_paint_scr(window, (*fieldp)->master_screen, 
			  enhance, 
			  recursep, 
			  do_base);

	(*fieldp)->row = temp;
    }

    if ((screen->skey == NULL) && screen->softkey)
	screen->skey = sm_skey_open(screen->parent, screen->softkey);

    if (screen->skey)
	sm_paint_skey(screen->skey);

    if (sm_use_menus) {
	SM_MENU	root;

	/* if (screen->menu) else ... just use built-in menus ... */
	if ((root = sm_menu_open(sm_builtin_screens(), "root")) != NULL)
	    sm_paint_menu(root);
    }
}

void sm_paint_scr ARG3(int, window, SM_SCREEN, screen, int, enhance) 
{
    int	do_base = 0;

    if (screen == NULL) {
	smio_clear(window);
	sm_screen_stack[window].screen = screen;
	return;
    }

    if (screen != sm_screen_stack[window].screen) {
	/* Then we need to re-paint fabric as well as contents */
	smio_clear(window);
	sm_screen_stack[window].screen = screen;
	do_base = 1;
    }
    
    if (sm_editing_master())
	enhance = 0;
    
    _sm_paint_scr(window, 
		  screen, 
		  enhance, 
		  /* recursep = */ 1, 
		  do_base);

    /* Pick out the master ... */
    if (sm_editing_master())
	_sm_paint_scr(window, 
		      sm_editing_master(), 
		      /* enhance = */ 1, 
		      /* recursep = */ 0,
		      0);
#if SM_XAW
    if (do_base && smio_using_xaw)
	smxaw_realise_form(screen);
#endif
}

/* Display no screen (blank out) */
void sm_null_scr ARG1(int, window)
{
    smio_clear(window);
    sm_screen_stack[window].screen = NULL;
    sm_screen_stack[window].detail_field = NULL;
}

SM_FIELD *_sm_seek_fld ARG2(SM_FIELD, last_field, SM_SCREEN, screen)
{
    SM_FIELD	*fieldp;

    assert(screen);

    /* This is set here so that key screens can also be set in the
       recursion */
    screen->changed = 0;

    if (last_field)
	for (fieldp = sm_first_fld(screen); 
	     fieldp && *fieldp; 
	     fieldp = sm_next_fld(fieldp)) {

	    if (*fieldp == last_field)
		return(fieldp);

	    if ((*fieldp)->master_screen) {
		SM_FIELD	*f;
		
		f = _sm_seek_fld(last_field, (*fieldp)->master_screen);
		if (f)
		    return(f);
	    }
	}
    return(NULL);
}

SM_FIELD *sm_next_fld_on_screen ARG2(SM_SCREEN, screen, SM_FIELD *, target_fieldp)
{
    int		x, y, 
		minx = 10000,
	 	miny = 10000,
    		target_x,
		target_y,
    		target_len;

    SM_FIELD	*fieldp, 
    		*best_fit = NULL, 
		*rec;

    if (screen == NULL)
	return(NULL);

    if ((target_fieldp == NULL) || (*target_fieldp == NULL)) {
	target_x = -1;
	target_y = -1;
	target_len = 1;
    } else {
	target_x = (*target_fieldp)->col;
	target_y = (*target_fieldp)->row;
	target_len = abs((*target_fieldp)->len);
    }

    for (fieldp = sm_first_fld(screen);
	 fieldp && *fieldp;
	 fieldp = sm_next_fld(fieldp)) {
	
	if (!((*fieldp)->hidden) &&
	    !((*fieldp)->protect) &&
	    (!sm_editing_master() || 
	     (sm_editing_master() == (*fieldp)->parent))) {
	    x = (*fieldp)->col;
	    y = (*fieldp)->row;
	    
	    if (y > target_y) {
		if ((y < miny) || ((y == miny) && (x < minx))) {
		    best_fit = fieldp;
		    minx = (*best_fit)->col;
		    miny = (*best_fit)->row;
		}
	    } else if (y == target_y)
		if (x > target_x + target_len) {
		    if ((y < miny) || ((y == miny) && (x < minx))) {
			best_fit = fieldp;
			minx = (*best_fit)->col;
			miny = (*best_fit)->row;
		    }
		}
	}

	if ((*fieldp)->master_screen) {
	    rec = sm_next_fld_on_screen((*fieldp)->master_screen, target_fieldp);
	    if (rec && *rec &&
		(((*rec)->row < miny) ||
		 (((*rec)->row == miny) && ((*rec)->col < minx)))) {
		best_fit = rec;
		minx = (*best_fit)->col;
		miny = (*best_fit)->row;
	    }
	}
	
    }
    return(best_fit);
}

SM_FIELD *sm_prev_fld_on_screen ARG2(SM_SCREEN, screen, SM_FIELD *, target_fieldp)
{
    int		x, y, 
		maxx = -10000,
	 	maxy = -10000,
    		target_x,
		target_y;

    SM_FIELD	*fieldp, 
    		*best_fit = NULL, 
		*rec;

    if (screen == NULL)
	return(NULL);

    if ((target_fieldp == NULL) || (*target_fieldp == NULL)) {
	target_x = 10000;
	target_y = 10000;
    } else {
	target_x = (*target_fieldp)->col;
	target_y = (*target_fieldp)->row;
    }

    for (fieldp = sm_first_fld(screen);
	 fieldp && *fieldp;
	 fieldp = sm_next_fld(fieldp)) {
	
	if (!((*fieldp)->hidden) &&
	    !((*fieldp)->protect) &&
	    (!sm_editing_master() || 
	     (sm_editing_master() == (*fieldp)->parent))) {
	    
	    x = (*fieldp)->col;
	    y = (*fieldp)->row;
	    
	    if (y < target_y) {
		if ((y > maxy) || ((y == maxy) && (x > maxx))) {
		    best_fit = fieldp;
		    maxx = (*best_fit)->col;
		    maxy = (*best_fit)->row;
		}
	    } else if (y == target_y)
		if (x < target_x) {
		    if ((y > maxy) || ((y == maxy) && (x > maxx))) {
			best_fit = fieldp;
			maxx = (*best_fit)->col;
			maxy = (*best_fit)->row;
		    }
		}
	}

	if ((*fieldp)->master_screen) {
	    rec = sm_prev_fld_on_screen((*fieldp)->master_screen, target_fieldp);
	    if (rec && *rec &&
		(((*rec)->row > maxy) ||
		 (((*rec)->row == maxy) && ((*rec)->col > maxx)))) {
		best_fit = rec;
		maxx = (*best_fit)->col;
		maxy = (*best_fit)->row;
	    }
	}
	
    }
    return(best_fit);
}

SM_FIELD *sm_first_fld_on_screen ARG1(SM_SCREEN, screen)
{
    return(sm_next_fld_on_screen(screen, NULL));
}

struct enter_scr_s {
    f_ptr		destructor;
    SM_SCREEN 		screen;
    SM_FIELD		*fieldp;
    int			window;
    SM_SCREEN		target_for_field_popups;
    int			return_on_mouse_events;
    int			target_col;
    int			return_on_SM_UP_C;
};

int enter_scr_destructor ARG1(struct enter_scr_s *, object)
{
    free(object);
    return(0);
}

static int sm_prep_scr ARG1(struct enter_scr_s *, enter_scr)
{
    SM_FIELD	*f;
    
#if SM_XAW
    /* window != 0 means that we are in a popup - ie in sm_prompt */
    if (smio_using_xaw && enter_scr->window) {
    } else
#endif
	sm_paint_scr(enter_scr->window, enter_scr->screen, /* enhance = */ 1);

    while ((*(enter_scr->fieldp))->hidden  ||
    	   (*(enter_scr->fieldp))->protect ||
	   (sm_editing_master() &&
	    (sm_editing_master() != (*(enter_scr->fieldp))->parent))) {
	if (((f = sm_next_fld_on_screen(enter_scr->screen, 
					enter_scr->fieldp)) != NULL) && *f)
	    enter_scr->fieldp = f;
	else
	    return(SM_NEXT_SCREEN_C);
    }
    
    if (sm_editing_master())
	sm_setup_fld(enter_scr->window,
		     *(enter_scr->fieldp), 
		     /* write_protect = */ 0,
		     enter_scr->target_for_field_popups,
		     enter_scr->target_col);
    else
	sm_setup_fld(enter_scr->window,
		     *(enter_scr->fieldp), 
		     /* write_protect = */ 
		     ((*(enter_scr->fieldp))->master_screen != NULL) ||
		     ((*(enter_scr->fieldp))->parent != enter_scr->screen),
		     enter_scr->target_for_field_popups,
		     enter_scr->target_col);
    return(0);
}

/* Find closest field above (or below) current one */
static SM_FIELD *
    sm_find_adjacent_field ARG4(int, 		window, 
				SM_SCREEN, 	screen, 
				SM_FIELD, 	field, 
				int, 		up)
{
    SM_FIELD	*f, *new_field = NULL;
    int		row, col, dist, d, down = !up;
    
    if (!field)
	return(NULL);

    smio_getyx(window, &row, &col);
    if ((up && (row > 0)) || down) {
	dist = 10000;
	new_field = NULL;
	for (f = sm_first_fld_on_screen(screen); 
	     f && *f; 
	     f = sm_next_fld_on_screen(screen, f)) {
	    if ((  up && ((*f)->row < row)) ||
		(down && ((*f)->row > row)))
		if ((d = abs((*f)->row - row)) < dist) {
		    new_field = f;
		    dist = d;
		}
	}
	
	if (new_field) {
	    row = (*new_field)->row;
	    dist = 10000;
	    for (f = sm_first_fld_on_screen(screen); 
		 f && *f; 
		 f = sm_next_fld_on_screen(screen, f)) {
		if ((*f)->row == row) {
		    if ((d = abs((*f)->col - col)) < dist) {
			new_field = f;
			dist = d;
		    }
		    
		    if ((d = abs((*f)->col + abs((*f)->len)-1 - col)) < dist) {
			new_field = f;
			dist = d;
		    }
		}
	    }
	}
    }
    return(new_field);
}

/* Allow user to fill in the screen */
CALLBACK(sm_enter_scr)
{
    struct enter_scr_s 	*enter_scr = (struct enter_scr_s *) callback_data;
    SM_FIELD		*fp;
    int			retval = 0, i;

    if (sm_fld_changed(*(enter_scr->fieldp)))
	(*(enter_scr->fieldp))->parent->changed = 1;

    smio_getyx(enter_scr->window, &i, &enter_scr->target_col);

    switch (command) {
    case SM_NEXT_FIELD_C:
    case SM_NEXT_LINE_C:
	enter_scr->target_col = 0;
	fp = NULL;
	if ((*(enter_scr->fieldp))->next_field)
	    fp = sm_fld_openp(enter_scr->screen, 
			     (*(enter_scr->fieldp))->next_field);

	if (fp == NULL)
	    fp = sm_next_fld_on_screen(enter_scr->screen, enter_scr->fieldp);

	if (fp && *fp) 
	    enter_scr->fieldp = fp;      
	else
	    retval = SM_NEXT_LINE_C;
	break;
	
    case SM_PREV_FIELD_C:
	enter_scr->target_col = 0;
	fp = sm_prev_fld_on_screen(enter_scr->screen, enter_scr->fieldp);
	if (fp && *fp)
	    enter_scr->fieldp = fp;
	break;
	
    case SM_HOME_SCREEN_C:
	enter_scr->target_col = 0;
	for (fp = enter_scr->fieldp; 
	     fp && *fp; 
	     fp = sm_prev_fld_on_screen(enter_scr->screen, fp))
	    enter_scr->fieldp = fp;
	break;
	
    case SM_END_SCREEN_C:
	enter_scr->target_col = 0;
	for (fp = enter_scr->fieldp; 
	     fp && *fp; 
	     fp = sm_next_fld_on_screen(enter_scr->screen, fp))
	    enter_scr->fieldp = fp;
	break;
	
    case SM_MOUSE_EVENT_C:
	{
	    int	mouse_button, mouse_row, mouse_col, mouse_delta;
	    
	    mouse_button = sm_enq_mouse(&mouse_row, 
					&mouse_col, 
					&mouse_delta);
	    fp = sm_pick_fld(enter_scr->screen, mouse_row, mouse_col);
	    while (fp && *fp && ((*fp)->hidden || (*fp)->protect))
	    	fp = sm_next_fld_on_screen(enter_scr->screen, fp);
	    if (fp) {
		enter_scr->fieldp = fp;
		sm_set_field(*fp);
		if (mouse_button & SM_MOUSE_UP)
		    sm_mouse_hit_on_fld(*fp, enter_scr->window);
		if (enter_scr->return_on_mouse_events)
		    retval = command;
	    }
	}
	break;
	
    case SM_CLEAR_SCREEN_C:
	enter_scr->target_col = 0;
	for (enter_scr->fieldp = 
	     sm_next_fld_on_screen((*(enter_scr->fieldp))->parent, 
				   enter_scr->fieldp); 
	     enter_scr->fieldp && *(enter_scr->fieldp); 
	     enter_scr->fieldp = 
	     sm_next_fld_on_screen((*(enter_scr->fieldp))->parent, 
				   enter_scr->fieldp)) {
	    if ((*(enter_scr->fieldp))->parent == enter_scr->screen) {
		sm_fld_init(*(enter_scr->fieldp));
		enter_scr->screen->changed = 1;
	    }
	}
	retval = command; /* Allow calling program to update 
			     its data structures too! */
	break;
	
    case SM_UP_C:
	if (enter_scr->screen->scrolling &&
	    ((*(enter_scr->fieldp))->row >= enter_scr->screen->scrolling -1) &&
	    enter_scr->return_on_SM_UP_C)
		retval = SM_UP_C;
	else if ((fp = sm_find_adjacent_field(enter_scr->window,
					     enter_scr->screen, 
					     *(enter_scr->fieldp), 
					     1)) != NULL)
	    enter_scr->fieldp = fp;
	else if (!(enter_scr->screen->scrolling) ||
		((*(enter_scr->fieldp))->row <
		 enter_scr->screen->scrolling - 1))
	    retval = SM_PREV_SCREEN_C;
	break;
	
    case SM_DOWN_C:
	if (enter_scr->screen->scrolling &&
	    ((*(enter_scr->fieldp))->row >= enter_scr->screen->scrolling - 1))
		retval = SM_DOWN_C;
	else if ((fp = sm_find_adjacent_field(enter_scr->window,
					     enter_scr->screen, 
					     *(enter_scr->fieldp), 
					     0)) != NULL)
	    enter_scr->fieldp = fp;
	else  if (!(enter_scr->screen->scrolling))
	    retval = SM_NEXT_SCREEN_C;
	break;
	
    default:
	retval = command;
    }

    if (retval == 0)
	retval = sm_prep_scr(enter_scr);
    return(retval);
}

/* Always called with a detail screen - master screens get drawn by
   sm_paint_scr. 
*/
int sm_setup_scr ARG7(int, 		window, 
		      SM_SCREEN, 	screen, 
		      SM_FIELD, 	init_field,
		      SM_SCREEN,	target_for_field_popups,
		      int,		return_on_mouse_events,
		      int,		target_column,
		      int,		return_on_SM_UP_C)
{
    struct enter_scr_s *enter_scr;

    assert(screen);

    enter_scr = (struct enter_scr_s *)xmalloc((INDEX) sizeof(struct enter_scr_s));
    enter_scr->destructor = enter_scr_destructor;

    enter_scr->screen = screen;
    enter_scr->window = window;
    enter_scr->target_for_field_popups = target_for_field_popups;
    enter_scr->return_on_mouse_events = return_on_mouse_events;
    enter_scr->target_col = target_column;
    enter_scr->return_on_SM_UP_C = return_on_SM_UP_C;

    screen->changed = 0;

    /* Try to put cursor on the same field that the user last used */
    if ((init_field == NULL) && screen->first_field)
	init_field = sm_fld_open(screen, screen->first_field);

    if (init_field) {
	enter_scr->fieldp = sm_first_fld(init_field->parent);
	while (enter_scr->fieldp &&
	       *enter_scr->fieldp &&
	       *enter_scr->fieldp != init_field)
	    enter_scr->fieldp = sm_next_fld(enter_scr->fieldp);
    } else
	enter_scr->fieldp = _sm_seek_fld(sm_get_field(), screen);

    if (enter_scr->fieldp == NULL)
	enter_scr->fieldp = sm_first_fld(screen);
    
    if ((enter_scr->fieldp == NULL) || (*(enter_scr->fieldp) == NULL)) {
	sm_paint_scr(window, screen, 1);
	return(sm_setup_fld(window, NULL, /* write_protect = */ 0, NULL, 0));
    }
    
    sm_add_callback(sm_enter_scr, enter_scr);
    return(sm_prep_scr(enter_scr));
}

/* Find which field the cursor is actually in (not just close to) */
SM_FIELD *sm_pick_fld ARG3(SM_SCREEN, screen, int, row, int, col)
{
    SM_FIELD	*fieldp;
    
    if (screen) {
        for (fieldp = sm_first_fld(screen); 
	     (fieldp && *fieldp);
	     fieldp = sm_next_fld(fieldp)) {

	    if (screen->scrolling)
		if ((*fieldp)->row >= screen->scrolling - 1)
		    if (row >= screen->scrolling - 1)
			if (col >= (*fieldp)->col)
			    if (col < (*fieldp)->col + abs((*fieldp)->len))
				return(fieldp);

            if ((row == (*fieldp)->row) &&
                (col >= (*fieldp)->col) &&
                (col < (*fieldp)->col + abs((*fieldp)->len)))
                return(fieldp);

	    if ((*fieldp)->master_screen) {
		SM_FIELD	*rec;
		
		rec = sm_pick_fld((*fieldp)->master_screen, row, col);
		if (rec && *rec)
		    return(rec);
	    }
        }
    }
    return(NULL);
}

void sm_edit_master ARG1(SM_FIELD, detail_field)
{
    if (detail_field) {
	sm_screen_stack[sm_top_of_screen_stack].detail_field = detail_field;
	sm_message("Editing master data set");
    } else {
	sm_message("Editing detail data set");
	sm_screen_stack[sm_top_of_screen_stack].detail_field = NULL;
    }
}

SM_SCREEN sm_editing_master()
{
    if (sm_screen_stack[sm_top_of_screen_stack].detail_field)
	return(sm_screen_stack[sm_top_of_screen_stack].detail_field->master_screen);
    else
	return(NULL);
}

SM_FIELD sm_detail_field()
{
    return(sm_screen_stack[sm_top_of_screen_stack].detail_field);
}

void sm_paint_part_scr ARG3(int, window, SM_SCREEN, screen, int, row)
{
    SM_FIELD		*fieldp;
    SM_BACKGROUND	*bgp;

    assert(screen);

    smio_clrline(window);
    bgp = sm_first_bg(screen);
    while (bgp && *bgp) {
        if ((*bgp)->row == row)
            sm_paint_bg(window, *bgp);
        bgp = sm_next_bg(bgp);
    }
    
    fieldp = sm_first_fld(screen);
    while (fieldp && *fieldp) {
        if ((*fieldp)->row == row)
            sm_paint_fld(window, 
			 (*fieldp)->c_value, 
			 *fieldp, 
			 /* enhance = */ 1, 1);
        fieldp = sm_next_fld(fieldp);
    }
}

SM_SCREEN sm_current_scr()
{
    return(sm_screen_stack[sm_top_of_screen_stack].screen);
}

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
